package ca.inrs.ete.app.shopx.provider;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.zip.CRC32;
import java.util.zip.Checksum;

import android.content.BroadcastReceiver;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.preference.PreferenceManager;
import android.util.Log;
import android.webkit.MimeTypeMap;

import ca.inrs.ete.app.shopx.SHOPPreferences;
import ca.inrs.ete.app.shopx.util.Constants;


public class DataProvider extends ContentProvider
{
	private static final String TAG = "SHOPLog.DataProvider";

	public static final Uri      CONTENT_URI_ = Uri.parse("content://" + Constants.CONTENT_AUTHORITY);
	public static final Uri      CONTENT_URI_FOLDERS = Uri.parse("content://" + Constants.CONTENT_AUTHORITY + "/folders");
	public static final Uri      CONTENT_URI_IMAGES  = Uri.parse("content://" + Constants.CONTENT_AUTHORITY + "/images");
	public static final String   CONTENT_TYPE_FOLDERS = ContentResolver.CURSOR_DIR_BASE_TYPE + "/" + Constants.CONTENT_VENDOR + ".folders";
	public static final String   CONTENT_TYPE_IMAGES  = ContentResolver.CURSOR_DIR_BASE_TYPE + "/" + Constants.CONTENT_VENDOR + ".images";

	public static final String[] CONTENT_PROVIDER_MIME = { "image/gif", "image/jpeg", "image/png"};

	private static final int FOLDERS  = 1;
	private static final int IMAGES   = 2;
	private static final int IMAGE_ID = 3;

	private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);

	static
	{
		sURIMatcher.addURI(Constants.CONTENT_AUTHORITY, "folders", FOLDERS);
		sURIMatcher.addURI(Constants.CONTENT_AUTHORITY, "images",  IMAGES);
		sURIMatcher.addURI(Constants.CONTENT_AUTHORITY, "images/#", IMAGE_ID);
	}	

	private class SyncReceiver extends BroadcastReceiver {
		@Override
		public void onReceive(Context context, Intent intent) {
			final Boolean sucess = intent.getBooleanExtra(Constants.MSG_SYNC_KEY_SUCCESS, false);
			if (! sucess) return;
			manageFoldersRollover();
		}
	}
	private SyncReceiver syncReceiver = new SyncReceiver();


	private void manageFoldersRollover()
	{
		final Context context = getContext();
		final SHOPPreferences prefs = new SHOPPreferences();
		prefs.readValues( PreferenceManager.getDefaultSharedPreferences(context) );
		final int dataBufferSize = prefs.getDataBufferSize();

		String[] dirs = getLocalDirs(context);
		int nbRegularFolders = dirs.length;
		if (nbRegularFolders > dataBufferSize)
		{
			for (int i = 0; i < dirs.length; ++i) {
				File folder = new File(dirs[i]);
				if (! isFolderRemoveable(folder) ) {
					dirs[i] = null;
					nbRegularFolders--;
				}
			}
			while (nbRegularFolders > dataBufferSize)
			{
				for (int i = 0; i < dirs.length; ++i) {
					if (dirs[i] != null) { 
						File folder = new File(dirs[i]);
						deleteFolderRecursively(folder);
						dirs[i] = null;
						nbRegularFolders--;
						break;
					}
				}
			}
		}
	}

	private static boolean isFolderRemoveable(File folder) {
		if (folder.isDirectory()) {
		    File[] files = folder.listFiles();
		    if (files != null) {
		        for (File f: files) {
		        	if (f.getName().equals(".noremove")) return false;
		        }
		    }
		}
		return true;
	}

	public static void deleteFolderRecursively(File folder) {
	    File[] files = folder.listFiles();
	    if (files != null) { //some JVMs return null for empty dirs
	        for (File f: files) {
	            if(f.isDirectory()) {
	            	deleteFolderRecursively(f);
	            } else {
	                f.delete();
	            }
	        }
	    }
	    folder.delete();
	}

	/**
	 * deleteFiles(...) 
	 */
	private int deleteFolders(Uri uri) {
		Log.v(TAG, String.format("deleteFiles(%s)", uri.toString()));

		String[] dirs = getLocalDirs(getContext());
		for (String dir : dirs) {
			deleteFolderRecursively(new File(dir));
		}

		return dirs.length;
	}


	/**
	 * deleteFiles(...) 
	 */
	private int deleteFiles(Uri uri) {
		Log.v(TAG, String.format("deleteFiles(%s)", uri.toString()));

		String dir = new String(uri.getQuery());
		dir = dir.substring( dir.indexOf("=")+1 );

		deleteFolderRecursively(new File(dir));

		return 1;
	}


	
	
	/**
	 * Return the mime type of the url or null.
	 * 
	 * Bug??: A version with
	 * 		String extension = MimeTypeMap.getFileExtensionFromUrl(url);
	 * doesn't work for certain file names. 
	 */
	private static String getMimeType(String url)
	{
		String type = null;
		try {
			String extension = url.substring(url.lastIndexOf(".")+1);
			if (extension != null) {
				MimeTypeMap mime = MimeTypeMap.getSingleton();
				type = mime.getMimeTypeFromExtension(extension);
			}
		} catch (Exception e) {
		}
		return type;
	}

	private static Long getFileId(String f)	{
		Checksum checksum = new CRC32();
		checksum.update(f.getBytes(), 0, f.length());
		return  checksum.getValue();
	}

	private static String[] getLocalDirs(Context ctxt)
	{
		ArrayList<String> lst = new ArrayList<String>();

		File baseDir = ctxt.getExternalFilesDir(null);
		if (baseDir == null) baseDir = ctxt.getFilesDir();

		for (File file : baseDir.listFiles()) {
			if (! file.isDirectory()) continue;
			String fileName = file.getName();
			if (fileName.charAt(0) != '2') continue;
			lst.add(String.format("%s/%s", baseDir, file.getName()));
		}
		Collections.sort(lst);

		return (lst.size() > 0) ? lst.toArray(new String[lst.size()]) : null;
	}

	private static String[] getFilesFromDir(Context ctxt, String dir)
	{
		ArrayList<String> lst = new ArrayList<String>();

		File baseDir = new File(dir);
		for (File file : baseDir.listFiles()) {
			String mimeType = getMimeType(file.getName());
			try {
				for (String t : CONTENT_PROVIDER_MIME) {
					if (t.compareTo(mimeType) == 0) {
						lst.add(String.format("%s/%s", dir, file.getName()));
						break;
					}
				}
			}
			catch (NullPointerException  e)	{
			}
		}
		Collections.sort(lst);

		return (lst.size() > 0) ? lst.toArray(new String[lst.size()]) : null;
	}

	private static String findFileById(Context ctxt, Long id) {
		for (String dir : getLocalDirs(ctxt)) {
			for (String f : getFilesFromDir(ctxt, dir)) {
				if (getFileId(f) == id) return f;
			}
		}
		return null;
	}

	/**
	 * queryFiles(...) return the image files contained in the last local directory
	 * or null if not data is present
	 */
	private Cursor queryOneFile(Uri uri) {
		Log.v(TAG, String.format("queryOneFile(%s)", uri.toString()));

		MatrixCursor c = null;

		long id = ContentUris.parseId(uri);
		String file = findFileById(getContext(), id);
		if (file != null) {
			c = new MatrixCursor(new String[] { "_id", "image", "_data" }, 1);
			c.addRow(new Object[] { id,
					ContentUris.withAppendedId(CONTENT_URI_IMAGES, id),
					String.format("file://%s", file) });
			c.setNotificationUri(getContext().getContentResolver(), uri);
		}

		return c;
	}

	/**
	 * queryFiles(...) return the image files contained in the last local directory
	 * or null if not data is present
	 */
	private Cursor queryFiles(Uri uri) {
		Log.v(TAG, String.format("queryFiles(%s)", uri.toString()));

		String dir = new String(uri.getQuery());
		dir = dir.substring( dir.indexOf("=")+1 );

		String[] files = null;
		MatrixCursor c = null;

		if (dir != null && ! dir.isEmpty() ) {
			files = getFilesFromDir(getContext(), dir);
		}
		if (files != null) {
			c = new MatrixCursor(new String[] { "_id", "image", "_data" }, files.length);
			long id = (long) 0;
			for (String f : files) {
				id += 1;
				c.addRow(new Object[] { id,
						ContentUris.withAppendedId(CONTENT_URI_IMAGES, id),
						String.format("file://%s", f) });
			}
			c.setNotificationUri(getContext().getContentResolver(), uri);
		}

		return c;
	}

	/**
	 * queryDirectory(...) return the last local directory
	 * or null if not data is present
	 */
	private Cursor queryFolders(Uri uri) {
		Log.v(TAG, String.format("queryFolders(%s)", uri.toString()));

		String[] files = null;
		MatrixCursor c = null;

		files = getLocalDirs( getContext() );
		if (files != null) {
			c = new MatrixCursor(new String[] { "_id", "directory", "_data" }, files.length);
			long id = (long) 0;
			for (String f : files) {
				id += 1;
				c.addRow(new Object[] { id,
						CONTENT_URI_IMAGES.buildUpon().appendQueryParameter("directory", f).build(),
						String.format("file://%s", f) });
			}
			c.setNotificationUri(getContext().getContentResolver(), uri);
		}

		return c;
	}


	@Override
	public boolean onCreate() {
		getContext().registerReceiver(syncReceiver, new IntentFilter(Constants.MSG_SYNC_TERMINATED));
		return true;
	}

	/**
	 * query(...) return the image files contained in the last local directory
	 * or null i not data is present
	 */
	@Override
	public Cursor query(Uri uri, String[] projection, String selection,	String[] selectionArgs, String sortOrder) {
		Log.i(TAG, String.format("query(%s)", uri.toString()));

		int match = sURIMatcher.match(uri);
		switch (match)
		{
		case FOLDERS:
			return queryFolders(uri);
		case IMAGES:
			return queryFiles(uri);
		case IMAGE_ID:
			return queryOneFile(uri);
		default:
			return null;
		}
	}

	@Override
	public ParcelFileDescriptor openFile(Uri uri, String mode)
			throws FileNotFoundException {
		Log.d(TAG, "openFile(" + uri + ", " + mode + ")");
		return super.openFileHelper(uri, mode);
	}

	@Override
	public Uri insert(Uri uri, ContentValues values) {
		throw new UnsupportedOperationException();
	}

	@Override
	public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
		throw new UnsupportedOperationException();
	}

	@Override
	public int delete(Uri uri, String selection, String[] selectionArgs) {

		Log.i(TAG, String.format("delete(%s)", uri.toString()));

		int match = sURIMatcher.match(uri);
		switch (match)
		{
		case FOLDERS:
			return deleteFolders(uri);
		case IMAGES:
			return deleteFiles(uri);
		case IMAGE_ID:
			throw new UnsupportedOperationException();
		default:
			return 0;
		}
	}

	@Override
	public String[] getStreamTypes(Uri uri, String mimeTypeFilter) {
		return null;
	}

	@Override
	public String getType(Uri uri) {
		int match = sURIMatcher.match(uri);
		switch (match)
		{
		case FOLDERS:
			return CONTENT_TYPE_FOLDERS;
		case IMAGES:
			return CONTENT_TYPE_IMAGES;
		case IMAGE_ID:
			return CONTENT_PROVIDER_MIME[2];
		default:
			return null;
		}
	}

}
